﻿package extremefx.drawing {
	import extremefx.IEquatable;	

	/**
	 * @author Marcelo Volmaro
	 */
	public final class Color implements IEquatable {
		public static const EMPTY:Color = new Color(0x00000000);
		private var _red:uint;
		private var _green:uint;
		private var _blue:uint;
		private var _alpha:uint;
		
		private var _h:uint;
		private var _s:uint;
		private var _v:uint;
		private var _dirty:Boolean;

		/**
		 * Initializes a new instance of the Color class.
		 * @param args [alpha, red, green, blue] | [red, green, blue] | [AARRGGBB uint] | no arguments for an empty color
		 */
		public function Color(...args) {
			_red = _green = _blue = 0; 
			_dirty = true;
			
			if (args.length) {
				if (args[0] is Color){
					var c:Color = Color(args[0]);
					_alpha = c._alpha;
					_red = c._red;
					_green = c._green;
					_blue = c._blue;
					
				} else {
					_alpha = 255;
					var i:uint = 0;
					switch (args.length) {
						case 4:
							_alpha = parseInt(args[i++] + "") & 0xff;
							
						case 3:
							_red = parseInt(args[i++] + "") & 0xff;
							_green = parseInt(args[i++] + "") & 0xff;
							_blue = parseInt(args[i++] + "") & 0xff;
							break;
							
						case 1:
							var argb:uint = args[0];
							
							_alpha = (argb >> 24) & 0xff;
							_red = (argb >> 16) & 0xff;
							_green = (argb >> 8) & 0xff;
							_blue = argb & 0xff;
					}
				}
				
			} else {
				_alpha = 0;
			}
		}

		public function get isEmpty():Boolean {
			return _alpha == 0 && _red == 0 && _green == 0 && _blue == 0;
		}

		/**
		 * Gets the 32-bit ARGB value of this Color structure.
		 * The byte-ordering of the 32-bit ARGB value is AARRGGBB. The most significant byte (MSB), represented by AA, is the alpha component value. The second, third, and fourth bytes, represented by RR, GG, and BB, respectively, are the color components red, green, and blue, respectively
		 * @return The 32-bit ARGB value of this Color.
		 */
		public function toArgb():uint {
			return ((_alpha & 0xff) << 24 | (_red & 0xff) << 16 | (_green & 0xff) << 8 | (_blue & 0xff));
		}
		
		/**
		 * Gets the 24-bit RGB value of this Color structure.
		 * The byte-ordering of the 24-bit RGB value is RRGGBB.
		 * @return The 24-bit RGB value of this Color.
		 */
		public function toRgb():uint {
			return ((_red & 0xff) << 16 | (_green & 0xff) << 8 | (_blue & 0xff));
		}

		public function get red():uint {
			return _red;
		}
		
		public function set red(pVal:uint):void{
			_red = pVal;
			_dirty = true;
		}

		public function get green():uint {
			return _green;
		}
		
		public function set green(pVal:uint):void{
			_green = pVal;
			_dirty = true;
		}

		public function get blue():uint {
			return _blue;
		}
		
		public function set blue(pVal:uint):void{
			_blue = pVal;
			_dirty = true;
		}

		public function get alpha():uint {
			return _alpha;
		}
		
		public function set alpha(pVal:uint):void{
			_alpha = pVal;
		}
		
		public function getHue():uint {
			_rgbToHsv();
			return _h;
		}

		public function getSaturation():uint {
			_rgbToHsv();
			return _s;
		}

		public function getBrightness():uint {
			_rgbToHsv();
			return _v;
		}
		
		public function setHsv(pHue:uint, pSaturation:uint, pBrightness:uint):void {
			_h = pHue;
			_s = pSaturation;
			_v = pBrightness;
			_hsvToRgb();
		}


		/**
		 * int32: Creates a Color structure from a 32-bit ARGB value.
		 * int32, Color: Creates a Color structure from the specified Color structure, but with the new specified alpha value. Although this method allows a 32-bit value to be passed for the alpha value, the value is limited to 8 bits.
		 * int32, int32, int32: Creates a Color structure from the specified 8-bit color values (red, green, and blue). The alpha value is implicitly 255 (fully opaque). Although this method allows a 32-bit value to be passed for each color component, the value of each component is limited to 8 bits.
		 * int32, int32, int32, int32: Creates a Color structure from the four ARGB component (alpha, red, green, and blue) values. Although this method allows a 32-bit value to be passed for each component, the value of each component is limited to 8 bits.
		 */
		public static function fromArgb(...pArgs):Color {
			var a0:* = pArgs[0];
			
			switch(pArgs.length) {
				case 1:
					return new Color(a0);
					
				case 2:
					var c1:Color = Color(pArgs[1]); 
					var c:Color = new Color();
					c._red = c1._red;
					c._blue = c1._blue;
					c._green = c1._green;
					c._alpha = pArgs[0];
					return c;
					
				case 3:
					return new Color(a0, pArgs[1], pArgs[2]);
					
				case 4:
					return new Color(a0, pArgs[1], pArgs[2], pArgs[3]);
			}
			
			return null;
		}

		public static function parse(pColor:String):Color {
			if (pColor == "transparent") return new Color();
			
			var p:Array = pColor.split("");
			if (p[0] == "#") {
				p.shift();
				
			} else if (p[1] == "x" && p[0] == "0") {
				p.shift();
				p.shift();
			}
			
			var r:uint, g:uint, b:uint, a:uint;
			var i:uint = 0;
			switch (p.length) {
				case 4:
					//argb
					a = parseInt(p[i] + p[i++], 16);
					
				case 3:
					r = parseInt(p[i] + p[i++], 16);
					g = parseInt(p[i] + p[i++], 16);
					b = parseInt(p[i] + p[i++], 16);
					break;
					
				case 8:
					a = parseInt(p[i++] + p[i++], 16);
					
				case 6:
					r = parseInt(p[i++] + p[i++], 16);
					g = parseInt(p[i++] + p[i++], 16);
					b = parseInt(p[i++] + p[i++], 16);
			}
			
			if (i == 4 || i == 8) {
				return fromArgb(a, r, g, b);
			}
			
			return fromArgb(r, g, b);
		}

		public function equals(pObj:*):Boolean {
			if (pObj is Color) {
				var c:Color = Color(pObj);
				return (c._alpha == _alpha && c._blue == _blue && c._green == _green && c._red == _red);
			}
			
			return false;
		}
		
		private var _useRYBModel:Boolean; 
		 
		public function get useRYBModel():Boolean { 
			return _useRYBModel; 
		} 
		
		public function set useRYBModel(pValue:Boolean): void { 
			_useRYBModel = pValue;
		}
		
		
		/**
		 * @return the color as an hex representation in the form of 0xaarrggbb
		 */
		public function toString(pUseAlpha:Boolean = true) : String {
			return "0x"+ (pUseAlpha ? _toHex(_alpha) : "")+_toHex(_red)+_toHex(_green)+_toHex(_blue);
		}
		
		private function _toHex(pNumber:uint):String {
			var a:String = pNumber.toString(16).toUpperCase();
			if (a.length == 1) a = "0"+a;
			return a;
		}

		private function _rgbToHsv():void {
			if (!_dirty) return;
			_dirty = false;
			
			var m:Number = Math.max(_red, _green, _blue);
			var delta:Number = m - Math.min(_red, _green, _blue);
			var h:Number, s:Number;
			h = s = 0;
			
			if (m) {
				s = Math.round(delta / m) * 100;
				
				if (_red == m) {
					h = (_green - _blue) / delta;
					
				} else if (_green == m) {
					h = 2 + (_blue - _red) / delta;
					
				} else if (_blue == m) {
					h = 4 + (_red - _green) / delta;
				}
				
				h = Math.min(Math.round(h * 60), 360);
				if (h < 0) h += 360;
			}
			
			_h = _useRYBModel ? _rgbToryb(h) : h;
			_s = s;
			_v = Math.round(m / 255) * 100;
		}

		private function _hsvToRgb():void{
			var r:Number, g:Number, b:Number, a:Number, c:Number, f:Number;
			
			var h:Number = _useRYBModel ? _rybTorgb(_h) : _h;
			
			h /= 360;
			var s:Number = _s / 100;
			var v:Number = _v / 100;
			
			var round:Function = Math.round;

			if(s > 0) { 
				if(h >= 1) h = 0;

				h = 6 * h; 
				f = h - Math.floor(h);
				a = round(255 * v * (1 - s));
				b = round(255 * v * (1 - (s * f)));
				c = round(255 * v * (1 - (s * (1 - f))));
				v = round(255 * v);

				switch(Math.floor(h)) {

					case 0: 
						r = v; 
						g = c; 
						b = a; 
						break;
						
					case 1: 
						r = b; 
						g = v; 
						b = a; 
						break;
						
					case 2: 
						r = a; 
						g = v; 
						b = c; 
						break;
						
					case 3: 
						r = a; 
						g = b; 
						b = v; 
						break;
						
					case 4: 
						r = c; 
						g = a; 
						b = v; 
						break;
					case 5: 
						r = v; 
						g = a; 
						b = b; 
						break;
				}
				
				_red = r;
				_green = g;
				_blue = b;
			} else {
				_red = _green = _blue = round(v * 255);
			}
		}

		public static function blend(pC1:Color, pC2:Color, pPosition:Number):Color {
			pPosition = Math.min(Math.max(pPosition, 0), 1);
			var oneMinPos:Number = 1 - pPosition;
			return Color.fromArgb((pC1._alpha * oneMinPos) + (pC2._alpha * pPosition), (pC1._red * oneMinPos) + (pC2._red * pPosition), (pC1._green * oneMinPos) + (pC2._green * pPosition), (pC1._blue * oneMinPos) + (pC2._blue * pPosition));
		}
		
		//FIXME: This is not working as expected...
		private static function _rgbToryb(pInHue:Number):Number {
			var v:Number;
			if(pInHue > 239 && pInHue < 361) {
				trace("1 - in:"+pInHue);
				v = (0.2257 + Math.sqrt(0.05094049 - 0.0088 * (155.18 - pInHue))) / 0.0044;
				
			} else if(pInHue > -1 && pInHue < 61) {
				trace("2 - in:"+pInHue);//45 bad... out must be 90, is 0
				v = (-0.8386 + Math.sqrt(0.70324996 - 0.0168 * pInHue)) / -0.0084;
				
			} else if(pInHue > 59 && pInHue < 181) {
				trace("3 - in:"+pInHue);//180 bad.. out must be 210, is 221
				v = (0.427 + Math.sqrt(0.182329 - 0.0188 * (45.166 - pInHue))) / 0.0094;
				
			} else if(pInHue > 179 && pInHue < 241) {
				trace("4 - in:"+pInHue);
				v = (-5.3235 + Math.sqrt(28.33965225 + 0.0336 * (-567.45 - pInHue))) / -0.0168;
			}
			
			v = Math.abs(v);
			return Math.round(v);
		}
		
		private static function _rybTorgb(pInHue:Number):Number {
			var v:Number;
			if(pInHue > 239 && pInHue < 361) {
				v = (0.0022 * Math.pow(pInHue, 2)) - (0.2257 * pInHue) + 155.18;
				
			} else if(pInHue > -1 && pInHue < 61) {
				v = (-0.0042 * Math.pow(pInHue, 2)) + (0.8386 * pInHue);
				
			} else if(pInHue > 59 && pInHue < 181) {
				v = (0.0047 * Math.pow(pInHue, 2)) - (0.427 * pInHue) + 45.166;
				
			} else if(pInHue > 179 && pInHue < 241) {
				v = (-0.0084 * Math.pow(pInHue, 2)) + (5.3235 * pInHue) - 567.45;
			}

			return Math.round(v);
		}
	}
}